/*
 * 人脸活体认证，通过摄像头
 * Created by RisingSun. 
 * User: MarkSpace 
 * Date: 20220419
 * Time: 11:05 
*/
layui.extend({
  setter: 'config' //配置模块
  ,faceapi: 'lib/extend/faceapi' //人脸识别
}).define(['setter','faceapi'],function(exports){ //提示：模块也可以依赖其它模块，

var setter = layui.setter;
var descriptors = { desc1: null, desc2: null };
var checkresult = 0 ; //20220531

var faceauth = {
    render:function($this, options){
        var divID;
        var that=this,models,timer,video,trackBox,canvasImg,sourceSize,matchedScore,mediaSize,threshold,audioStatus;

        this.divID = $this;
        this.threshold =options.threshold ? options.threshold :  0.49;
        this.models = options.models ? options.models : '/static/lay/lib/models';
        this.timer = options.timer ? options.timer : 300;  // 输入类型，图片，视频，摄像头视频流
        this.sourceSize = options.sourcesize ? options.sourcesize : 320;  //输入的数据源大小，
        this.matchedScore = options.matchedscore ? options.matchedscore : 0.9;
        this.mediaSize  = options.mediasize ? options.mediasize : { width: 640, height:360 };
        this.imgUrl = options.imgurl ? options.imgurl : '/static/lay/lib/models/ceshi2.png';
        this.audioStatus = options.audio ? options.audio : false;

        that.resize();

        this.video = document.querySelector('#video');
        this.trackBox = document.querySelector('#trackBox');
        this.canvasImg = document.querySelector('#canvasImg');
        this.retryBtn = document.querySelector('#retry');
        this.compareBtn = document.querySelector('#compare');
        this.textcon = document.querySelector('#textcon');

        that.drawViewFinder();
        
        Promise.all([
           faceapi.nets.tinyFaceDetector.loadFromUri(this.models),
           faceapi.nets.faceLandmark68Net.loadFromUri(this.models),
           faceapi.nets.faceRecognitionNet.loadFromUri(this.models) 
        ]).then(that.startVideo());

        that.bindEvents();
        return checkresult; //20220531
    },
    /*设置相关容器*/
    resize:function() {
        const wraper = document.querySelector(this.divID);
        wraper.setAttribute("style", 'top:70px;width:'+`${this.mediaSize.width}px`+'; height:'+ `${this.mediaSize.height}px`+';position: absolute; left:50%;right: 50%;transform: translateX(-50%);display: flex;align-items: center;justify-content: center;');
        
        const divp=document.createElement("p");
        divp.setAttribute("id", 'titlecon');
        divp.setAttribute("style", 'position: absolute;top:0px; z-index:101; font-size:16px; color:#fff');
        divp.innerHTML='人脸识别';
        wraper.appendChild(divp);

        const divps=document.createElement("p");
        divps.setAttribute("id", 'textcon');
        divps.setAttribute("style", 'position: absolute;bottom:20px; z-index:101; font-size:20px; color:#fff;');
        divps.innerHTML='请保持脸部在取景框内';
        wraper.appendChild(divps);

        const img=document.createElement("img");
        img.setAttribute("id", 'img');
        img.setAttribute("style", 'display:none;');
        wraper.appendChild(img);

        const video=document.createElement("VIDEO");
        video.setAttribute("width", this.mediaSize.width);
        video.setAttribute("height", this.mediaSize.height);
        video.setAttribute("autoplay", true);
        video.setAttribute("muted", true);
        video.setAttribute("style", 'z-index:10;position: absolute;');
        video.setAttribute("id", 'video');
        wraper.appendChild(video);

        const trackBox=document.createElement("canvas");
        trackBox.setAttribute("id","trackBox");
        trackBox.setAttribute("style", 'z-index:100;width:'+`${this.mediaSize.width}px`+'; height:'+ `${this.mediaSize.height}px`+';position: absolute; ');
        wraper.appendChild(trackBox);

        const canvasImg=document.createElement("canvas");
        canvasImg.setAttribute("id","canvasImg");
        canvasImg.setAttribute("style", 'z-index:99; position: absolute; left:50%;right: 50%;transform: translateX(-50%);');
        wraper.appendChild(canvasImg);

        const retry=document.createElement("button");
        retry.setAttribute("id", 'retry');
        retry.setAttribute("class", 'layui-btn');
        retry.setAttribute("style", 'position: absolute;top:100%;margin-top: 20px;visibility:hidden;margin-left:0px');
        retry.innerHTML='重新验证';
        wraper.appendChild(retry);

        const compare=document.createElement("button");
        compare.setAttribute("id", 'compare');
        compare.setAttribute("class", 'layui-btn');
        compare.setAttribute("style", 'position: absolute;top:100%;margin-top: 20px;margin-left:0px');
        compare.innerHTML='开始验证';
        wraper.appendChild(compare);

    },
    /** @name 绘制取景框 */
    drawViewFinder() {    
    const context = this.canvasImg.getContext("2d");
    if (!context) return;
    this.canvasImg.setAttribute('width', this.mediaSize.width - 120);
    this.canvasImg.setAttribute('height', this.mediaSize.height - 120);
      // topleft
      context.beginPath();//开启当前的绘制路径
      context.moveTo(0, 0);
      context.lineTo(50,0);
      context.moveTo(0,0);
      context.lineTo(0,50);
      context.lineWidth = 4;
      context.strokeStyle = "#1e9fff";
      context.stroke();//沿着坐标点的路径顺序绘制直线  
      context.closePath();   //关闭当前的绘制路径

      // topright
      context.beginPath();//开启当前的绘制路径
      context.moveTo(this.canvasImg.width,0);
      context.lineTo(this.canvasImg.width-50,0);
      context.moveTo(this.canvasImg.width, 0);
      context.lineTo(this.canvasImg.width,50);
      context.lineWidth = 4;
      context.strokeStyle = "#1e9fff"; 
      context.stroke();//沿着坐标点的路径顺序绘制直线
      context.closePath();   //关闭当前的绘制路径

      // bottomleft
      context.beginPath();//开启当前的绘制路径
      context.moveTo(0,this.canvasImg.height);
      context.lineTo(0,this.canvasImg.height-50);
      context.moveTo(0,this.canvasImg.height);
      context.lineTo(50,this.canvasImg.height);
      context.lineWidth = 4;
      context.strokeStyle = "#1e9fff"; 
      context.stroke();//沿着坐标点的路径顺序绘制直线
      context.closePath();   //关闭当前的绘制路径

      // bottomright
      context.beginPath();//开启当前的绘制路径
      context.moveTo(this.canvasImg.width,this.canvasImg.height);
      context.lineTo(this.canvasImg.width,this.canvasImg.height-50);
      context.moveTo(this.canvasImg.width,this.canvasImg.height);
      context.lineTo(this.canvasImg.width-50,this.canvasImg.height);
      context.lineWidth = 4;
      context.strokeStyle = "#1e9fff"; 
      context.stroke();//沿着坐标点的路径顺序绘制直线
      context.closePath();   //关闭当前的绘制路径

    },
    /** @name 采集视频流 */
    startVideo:function() {
        if (navigator.mediaDevices.getUserMedia || navigator.getUserMedia || navigator.webkitGetUserMedia || navigator
            .mozGetUserMedia) {
            //调用用户媒体设备, 访问摄像头
            this.getUserMedia({
                video: {
                    width: this.mediaSize.width,
                    height: this.mediaSize.height
                },
                audio: this.audioStatus  //不适用音频
            }, this.success, this.mediaErrorCallback);
        } else {
            layer.alert('不支持访问用户媒体');
        }
    },
    /** @name 获取视频流 */
    getUserMedia:function(constraints, success, error) {
            if (navigator.mediaDevices.getUserMedia) {
                //最新的标准API
                navigator.mediaDevices.getUserMedia(constraints).then(success).catch(error);
            } else if (navigator.webkitGetUserMedia) {
                //webkit核心浏览器
                navigator.webkitGetUserMedia(constraints, success, error)
            } else if (navigator.mozGetUserMedia) {
                //firfox浏览器
                navigator.mozGetUserMedia(constraints, success, error);
            } else if (navigator.getUserMedia) {
                //旧版API
                navigator.getUserMedia(constraints, success, error);
            }
        },
    /** @name 成功 */
    success:function(stream) {
            //兼容webkit核心浏览器
            let CompatibleURL = window.URL || window.webkitURL;
            //播放
            video.srcObject = stream;
            video.play();
    },
    /*获取媒体流错误处理*/
    mediaErrorCallback:function(error) {
        const errorMap = {
           'NotSupportedError':'浏览器不支持http访问用户媒体',
           'NotAllowedError': '摄像头已被禁用，请在当前浏览器设置中开启后重试',
           'AbortError': '硬件问题，导致无法访问摄像头',
           'NotFoundError': '未检测到可用摄像头',
           'NotReadableError': '操作系统上某个硬件、浏览器或者网页层面发生错误，导致无法访问摄像头',
           'OverConstrainedError': '未检测到可用摄像头',
           'SecurityError': '摄像头已被禁用，请在系统设置或者浏览器设置中开启后重试',
           'TypeError': '类型错误，未检测到可用摄像头'
          };
        if (errorMap[error.name]) {
          layer.alert(errorMap[error.name]);
        }
    },

    /*人脸识别弹框的相关事件绑定*/
    bindEvents() {
    // "重新验证" 按钮
        this.retryBtn.onclick = () => {
            location.reload();
        };
        // "开始验证" 按钮
        this.compareBtn.onclick = () => {
          //显示canvas
          this.show(this.trackBox).show(this.canvasImg).show(this.retryBtn).hide(this.compareBtn);
          this.textcon.innerHTML='识别中···';
          // 获取当前视频流中的图像，并通过 canvas 绘制出来
          this.checkSingle();
        };
    },
    /** @name 隐藏 */
    hide(el) {
        el.style.visibility = 'hidden';
        return this;
    },
    /** @name 显示 */
    show(el) {
        el.style.visibility = 'visible';
        return this;
    },

    /** @name 精确验证人脸 */
    async checkSingle() {
        const displaySize = { width: this.video.width, height: this.video.height };
        faceapi.matchDimensions(this.trackBox, displaySize);

        check=setInterval(async () => {
            var state=[];
                 const detections = await faceapi.detectSingleFace(video, new faceapi.TinyFaceDetectorOptions({
                 inputSize: this.sourceSize,   // 输入的数据源大小，这个值越小，处理速度越快。常用值：128, 160, 224, 320, 416, 512, 608
                 scoreThreshold: this.matchedScore // 用于过滤边界的分数阈值，大于等于这个值才被认为检测到了人脸，然后返回一个detection对象
              })).withFaceLandmarks(); //检测视频或者图片中全部脸
              if(detections){
              const resizedDetections = faceapi.resizeResults(detections, displaySize);
              this.trackBox.getContext('2d').clearRect(0, 0, this.trackBox.width, this.trackBox.height);
              faceapi.draw.drawDetections(this.trackBox, resizedDetections);
                    if (detections.detection.score > this.matchedScore) {
                          this.textcon.innerHTML='检测到人脸···';
                          clearInterval(check);
                          await setTimeout(this.checkMouth(), this.timer);
                        }
                }
                
           }, this.timer);

    },
    /*精确验证嘴巴动作*/
     checkMouth() {
        const displaySize = { width: this.video.width, height: this.video.height };
        faceapi.matchDimensions(this.trackBox, displaySize);
        var i=[];
        check=setInterval(async () => {
                 const detections = await faceapi.detectSingleFace(video, new faceapi.TinyFaceDetectorOptions({
                 inputSize: this.sourceSize,   // 输入的数据源大小，这个值越小，处理速度越快。常用值：128, 160, 224, 320, 416, 512, 608
                 scoreThreshold: this.matchedScore // 用于过滤边界的分数阈值，大于等于这个值才被认为检测到了人脸，然后返回一个detection对象
              })).withFaceLandmarks(); //检测视频或者图片中人脸
              if(detections){
              const resizedDetections = faceapi.resizeResults(detections, displaySize);
                    if (detections.detection.score > this.matchedScore) {
                          this.textcon.innerHTML='请张张嘴···';
                          i.push(parseInt(Math.abs(detections.landmarks.relativePositions[57].y*10000-detections.landmarks.relativePositions[51].y*10000)));
                          var sum=0;
                          i.forEach((item)=>{ sum += item;});
                          if((sum/i.length)/1000>1.3 ){
                            clearInterval(check);
                            this.canvasImg.getContext('2d').drawImage(this.video, 0, 0, this.canvasImg.width, this.canvasImg.height);
                            this.hide(this.canvasImg);
                            //获取对比图表
                            document.getElementById('img').src = this.canvasImg.toDataURL('image/png');
                            this.run();
                            this.video.pause();
                            return;
                          }
                        }
                }
           }, this.timer)

    },

    /** @name 对比人脸结果 */
    updateResult() {
      const distance = faceapi.utils.round(
        faceapi.euclideanDistance(descriptors.desc1, descriptors.desc2)
      )
      if (distance > this.threshold) {
       this.textcon.innerHTML='人脸认证失败';
       console.log(distance,'不是同一人');
       return checkresult = 0;
      }else{
       this.textcon.innerHTML='人脸认证成功';
       console.log(distance,'同一人');
       return checkresult = 1;
      }
      
    },
    /** @name 对比人脸 */
    async run() {
      await faceapi.nets.faceRecognitionNet.loadFromUri(this.models);
      const input = await faceapi.fetchImage(this.imgUrl);
      console.log(this.imgUrl);
      descriptors[`desc1`] = await faceapi.computeFaceDescriptor(input);
      descriptors[`desc2`] = await faceapi.computeFaceDescriptor(document.getElementById('img'));
      this.updateResult();
    }
}

exports('faceauth', faceauth);
});

